using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Windows.Forms;
using System.Drawing;
using ClientGUI.Widgets.Support;
using ClientGUI.Modules.Drawing;
using ClientGUI.Util;
using System.Threading;

using OWED.Document;

namespace ClientGUI
{
    /// <summary>
    /// Used when a module is selected to inform any interested listeners (properties pannel ect).
    /// </summary>
    /// <param name="m"></param>
    public delegate void ModuleSelectedEvent(Module m);

    /// <summary>
    /// This is the main class for displaying a documnt.  It handles everything
    /// from updating the screen to forcing the modules to size themselves.
    /// </summary>
    public class DisplayDocument : Control
    {
        #region member variables
        /// <summary>
        /// Configuration of this display
        /// </summary>
        Configuration my_config;

        List<ChangeAtom> my_proposal;

        /// <summary>
        /// Context to draw onto
        /// </summary>
        BufferedGraphicsContext context;
        /// <summary>
        /// Graphics to do the real drawing
        /// </summary>
        BufferedGraphics my_graphics;

        /// <summary>
        /// Module that currently has the "focus" in our gui
        /// </summary>
        Module currentlySelected;

        /// <summary>
        /// Module that currently has the "hoover" in our gui
        /// </summary>
        Module currentlyHoover;

        /// <summary>
        /// A list to all roots of the forest of modules that make up this
        /// display document.
        /// </summary>
        List<Module> my_modules;

        /// <summary>
        /// Current top, will be between 0 and max_height-this.Height.
        /// </summary>
        int my_current_top;

        /// <summary>
        /// Maximum height reachable by the document
        /// </summary>
        int my_max_height;

        /// <summary>
        /// List of Modules that are currently visible, along with their
        /// regions.
        /// </summary>
        List<DisplayElement> my_current_displayed;

        //UserTextEntry my_focused_box = null;

        /// <summary>
        /// Called whenever a module gets selected.
        /// </summary>
        ModuleSelectedEvent my_mod_select_event;


        /// <summary>
        /// Scroll bars are fun?
        /// </summary>
        VScrollBar scroll;

        /// <summary>
        /// The backing document that created this display.
        /// </summary>
        XmlDocument my_document;
        #endregion


        static int SCROLLPX = 20;
        /// <summary>
        /// Constructor to make Visual Studio happy.
        /// </summary>
        public DisplayDocument()
            : base()
        {
            context = new BufferedGraphicsContext();
            my_graphics = context.Allocate(this.CreateGraphics(),
                            new Rectangle(0, 0, this.Width, this.Height));
            scroll = new VScrollBar();
            scroll.Maximum = 0;
            scroll.Dock = DockStyle.Right;
            scroll.Scroll += new ScrollEventHandler(scroll_Scroll);
            this.Controls.Add(scroll);
            scroll.Show();
            Paint += new PaintEventHandler(DisplayDocument_Paint);

            Resize += new EventHandler(DisplayDocument_Resize);


            my_current_displayed = new List<DisplayElement>();

            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.Opaque, true);
            
        }


 
        /// <summary>
        /// List of ChangeAtoms that have been generated by the editing of the document.  This can be used
        /// to recreate the document given just the original and the list.
        /// </summary>
        public List<ChangeAtom> Changes
        {
            get
            {
                return my_proposal;
            }
            set
            {
                my_proposal = value;
            }
        }

        /// <summary>
        /// Remove the currently selected module and update the in-memory XML file.
        /// </summary>
        public void RemoveSelected()
        {
            currentlySelected.Parent.Children.Remove(currentlySelected);
            CalculateSpace();
            Redraw();
        }

        #region DisplayDocument Events
        /// <summary>
        /// Module selected event exported for any interested parties.
        /// </summary>
        public ModuleSelectedEvent ModuleSelected
        {
            get
            {
                return my_mod_select_event;
            }
            set
            {
                my_mod_select_event += value;
            }
        }

        #endregion

        #region DisplayDocument Event Handlers

        /// <summary>
        /// This adds item into the tree as the last child of "at"
        /// However, it also animates the add to give the user a visual queue.
        /// </summary>
        /// <param name="at">The parent to add under</param>
        /// <param name="item">The module to add</param>
        public void AddAt(Module at, Module item)
        {
            Blank b = new Blank(this, item);
            at.UnSelect();
            at.Children.Add(item);
            CalculateSpace(); //makin item's height correct.
            at.Children.Remove(item);
            CalculateSpace();

            at.Children.Add(b);
            AnimatorCallback a = delegate(int val)
            {
                b.SetHeight(val);
                Refresh();
            };
           
            Animator an = new Animator(0, item.Height + item.Height/2, 300, a);
            an.Animate();

            at.Children.Remove(b);
            at.Select();
            at.Children.Add(item);
            
            CalculateSpace();
            Redraw();
        }

        /// <summary>
        /// Scroll handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void scroll_Scroll(object sender, ScrollEventArgs e)
        {
            //we max out at 900 to avoid windows scrolling nonsense
            my_current_top = e.NewValue*SCROLLPX;
//            float percent = (float)Math.Min(e.NewValue, 900) / 900f;
//            my_current_top = Math.Min((int)(percent * (float)my_max_height), my_max_height - 2);
#if DEBUG
            Console.WriteLine("Set top to " + my_current_top + " scrollbar is at " + 0 + 
                "% the display height max is " + my_max_height + " the scroll bar is *actually* at" +
                e.NewValue);
#endif
            this.Invalidate();
        }

        /// <summary>
        /// This is called when the mouse "hovers" over a specific point in the display.  It
        /// calculates the most specific module and informs it that it has been hovered.
        /// </summary>
        /// <param name="x">x cord - screen cords</param>
        /// <param name="y">y cord - screen cords</param>
        public new void MouseHover(int x, int y)
        {
#if DEBUG
            Console.WriteLine("************************************************************");
            Console.WriteLine("DisplayDocument.ClickEvent: (x, y) = (" + x + ", " + y + ")");
#endif

            DisplayElement best = null;
            int besty = 0;
            foreach (DisplayElement element in my_current_displayed)
            {
#if DEBUG
                Console.WriteLine("DisplayDocument.Click: Checking Element: " + element);
#endif
                Rectangle region = element.Region;
                if (region.Y >= besty)
                {

#if DEBUG
                    Console.WriteLine("DisplayDocument.Click: ELEMENT IS CANDIDATE");
#endif
                    if (region.X < x &&
                        region.Y < y &&
                        region.Right > x &&
                        region.Bottom > y)
                    {
#if DEBUG
                        Console.WriteLine("DisplayDocument.Click: ELEMENT IS SELECTED");
#endif
                        best = element;
                    }
                }
            }
            //if we clicked nothing
            if (best == null)
            {

#if DEBUG
                Console.WriteLine("DisplayDocument.Click: No element selected.");
#endif
                if (currentlyHoover != null)
                {
                    currentlyHoover.Hoover = false;
                    currentlyHoover = null;
                }
                return;
            }


            Module handle = best.Displayed;
#if DEBUG
            Console.WriteLine("DisplayDocument.Click: Selected: " + best);
            Console.WriteLine("DisplayDocument.Click: Module.ClickEvent returned: " + handle);
#endif
            if (!handle.Equals(currentlyHoover))
            {
                //Console.WriteLine(handle);
                if (currentlyHoover != null)
                {
                    currentlyHoover.Hoover = false;
                }
                handle.Hoover = true;
                currentlyHoover = handle;
                //Console.WriteLine(handle);
            }
        }

        /// <summary>
        /// Cause the backing buffer to be updated on a resize.  This event makes
        /// no attempt to keep previously drawn information and invalidates the entire control.
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void DisplayDocument_Resize(object sender, EventArgs e)
        {
            if (Width > 0 && Height > 0)
            {
                if (my_graphics != null)
                    my_graphics.Dispose();
                my_graphics = context.Allocate(this.CreateGraphics(),
                    new Rectangle(0, 0, this.Width, this.Height));
                CalculateSpace();
                Redraw();
            }
        }

        /// <summary>
        /// Return the most specific module for a screen cord.
        /// </summary>
        /// <param name="x">x cord - screen</param>
        /// <param name="y">y cord - screen</param>
        /// <returns></returns>
        public DisplayElement ModuleAtCoordinate(int x, int y)
        {
            DisplayElement best = null;
            int besty = 0;
            foreach (DisplayElement element in my_current_displayed)
            {
#if DEBUG
                Console.WriteLine("DisplayDocument.Click: Checking Element: " + element);
#endif
                Rectangle region = element.Region;
                if (region.Y >= besty)
                {

#if DEBUG
                    Console.WriteLine("DisplayDocument.Click: ELEMENT IS CANDIDATE");
#endif
                    if (region.X < x &&
                        region.Y < y &&
                        region.Right > x &&
                        region.Bottom > y)
                    {
#if DEBUG
                        Console.WriteLine("DisplayDocument.Click: ELEMENT IS SELECTED");
#endif
                        best = element;
                    }
                }
            }
            return best;
        }

        /// <summary>
        /// This method calculates which module is selected, and then performs two actions:
        /// 
        /// 1.  It de-selects the previous module (which may cause a AtomicChange to be created
        ///     if it was being edited).
        /// 2.  It selects the new module.
        /// 3.  It informs all listeners of ModuleFocusChangeEvent of the update.
        /// </summary>
        /// <param name="x">x cord that the screen was clicked at</param>
        /// <param name="y">y cord the screen was clicked at</param>
        /// <returns>The selected module by this click</returns>
        public new Module Click(int x, int y)
        {

#if DEBUG
            Console.WriteLine("************************************************************");
            Console.WriteLine("DisplayDocument.ClickEvent: (x, y) = (" + x + ", " + y + ")");
#endif

            DisplayElement best = ModuleAtCoordinate(x, y);
            //if we clicked nothing
            if (best == null)
            {

#if DEBUG
                Console.WriteLine("DisplayDocument.Click: No element selected.");
#endif
                UnselectCurrent();
                my_mod_select_event(null);
                this.Invalidate();
                return null;
            }


            Module handle = best.Displayed.ClickEvent(x - best.Region.X, y - best.Region.Y);
#if DEBUG
            Console.WriteLine("DisplayDocument.Click: Selected: " + best);
            Console.WriteLine("DisplayDocument.Click: Module.ClickEvent returned: " + handle);
#endif
            if (!handle.Equals(currentlySelected))
            {
                UnselectCurrent();
                handle.Select();
                currentlySelected = handle;
                if (my_mod_select_event != null)
                {
                    my_mod_select_event(currentlySelected);
                }
            }
            return best.Displayed;
        }

        /// <summary>
        /// Unselect the current module.
        /// </summary>
        public void UnselectCurrent()
        {
            if (currentlySelected != null)
            {
                my_proposal.AddRange(currentlySelected.UnSelect());
                currentlySelected = null;
                if (my_mod_select_event != null)
                {
                    my_mod_select_event(currentlySelected);
                }
            }
        }

        /// <summary>
        /// Select the specified module.
        /// </summary>
        /// <param name="m"></param>
        public void Select(Module m)
        {
            if (m == null)
            {
                return;
            }
            if (m.Equals(currentlySelected))
            {
                return;
            }
            m.Parent.Space(m.Parent.Width);
            UnselectCurrent();
            currentlySelected = m;
            if (my_mod_select_event != null)
            {
                my_mod_select_event(currentlySelected);
            }
            m.Select();
        }

        /// <summary>
        /// A simple paint event, it simply draws the buffer on this control.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void DisplayDocument_Paint(object sender, PaintEventArgs e)
        {
            Draw();
            my_graphics.Render(CreateGraphics());
        }
        #endregion

        #region RequestTextEntry methods


        #endregion

        #region Configuration of this renderer
        /// <summary>
        /// Get the configuration of this control.
        /// </summary>
        public Configuration Config
        {
            get
            {
                return my_config;
            }
            set
            {
                my_config = value;
            }
        }


        #endregion


        /// <summary>
        /// 
        /// </summary>
        /// <param name="distance">less than 0 to go up, more than 0 to go down</param>
        public void Scroll(int distance)
        {
            my_current_top += distance;
            if (my_current_top < 0)
                my_current_top = 0;
            scroll.Value = my_current_top / SCROLLPX;
        }

        /// <summary>
        /// Scrolls the display to have this module at the top.
        /// Uses an animation which takes logn time (real time) to complete.
        /// </summary>
        /// <param name="mod">the module to place at the top.</param>
        public void ScrollTo(Module mod)
        {
            int space_above = 0;
            Module up = mod.Parent;
            Module child = mod;
            while (up != null)
            {
                for (int i = 0; i < up.Children.Count; ++i)
                {
                    if (up.Children[i] == child)
                        break;
                    space_above += up.Children[i].Height;
                }
                child = up;
                up = up.Parent;
            }

            AnimatorCallback callback = delegate(int val)
            {
                my_current_top = val;
                scroll.Value = my_current_top / SCROLLPX + scroll.Minimum;
                Redraw();
                Refresh();
            };
            Animator a = new Animator(my_current_top, space_above, 500, callback, 2);
            a.Animate();
        }

        /// <summary>
        /// Gets the current document.
        /// Note:  This is not accurate to what's displayed and
        /// is for internal use.  Please use GetDocument()
        /// </summary>
        public XmlDocument Document
        {
            get
            {
                return my_document;
            }
        }

        #region drawing code
        /// <summary>
        /// Informs the control that a change has been made to a component.
        /// </summary>
        public void Redraw()
        {
            this.Invalidate();
        }

        /// <summary>
        /// Used to calculate the space of ALL modules know at this time.  It should be called when
        /// the space has not been laid out yet at all.  Modules should update their space, and their
        /// parents space, when they are resized.
        /// </summary>
        public void CalculateSpace()
        {
            if (my_modules == null) return;

            int height = 0;
            foreach (Module kid in my_modules)
            {
                kid.Space(Width - scroll.Width - 4);
                height += kid.Height;
            }
            my_max_height = height;
        }

        /// <summary>
        /// Draw to the backing GraphicsContext.  This is where the layout code is performed.
        /// 
        /// It should be called when the current display may be stale in any way.  All repaint events
        /// know how to call this when needed.
        /// </summary>
        private void Draw()
        {
#if DEBUG
            Console.WriteLine("Draw called");
#endif

            //we don't display anything unless we add it later
            my_current_displayed.Clear();

            //draw a background to start...
            my_graphics.Graphics.FillRectangle(new SolidBrush(Color.White),
                new Rectangle(0, 0, this.Width, this.Height));
            //if we have no children, just display the background and exit
            if (my_modules == null)
            {
                my_graphics.Render(CreateGraphics());
                return;
            }
            //TODO: Change this to not re-compute every time
            //CalculateSpace();
            int y = 0;
            int height = 0;
            foreach (Module cur in my_modules)
            {
                y = RecursiveDraw(cur, y + cur.TopSpace, 2);
                height += cur.Height;
            }
            scroll.Maximum = height/SCROLLPX;
            scroll.Maximum += (Height/SCROLLPX)/2;
            //scroll.Maximum -= scroll.LargeChange/SCROLLPX;
            GC.Collect(); //keeps the memory usage down
        }

        /// <summary>
        /// The meat of the layout and display engine.  This beast of a method tells any Module
        /// that intersects the display area to Composite itself on a proper clipframe.  It does all
        /// layout math.
        /// 
        /// Furter, any control that is displayed will recursively call this method on all it's children
        /// so that they may display.
        /// </summary>
        /// <param name="cur">Current module to evaluate, if it intersects the screen, it will be 
        /// compositioned onto the screen.</param>
        /// <param name="y">Top edge of the current Module in a full layout (document cords)</param>
        /// <param name="x">Left edge of the current Module in the full layout (document cords)</param>
        /// <returns></returns>
        private int RecursiveDraw(Module cur, int y, int x)
        {
            int top = y;
            int bottom = y + cur.Height;

            int display_top = my_current_top;
            int display_bottom = my_current_top + this.Height;

#if DEBUG
            Console.WriteLine("DisplayDocument.RecursiveDraw: Attempting to draw " +
                cur.ToString());
            Console.WriteLine("DisplayDocument.RecursiveDraw: (top, bottom) = (" + top + ", " + bottom + 
                ") onto (d_top, d_bottom) = (" + display_top + ", " + display_bottom + ")"); 
#endif
            //if either range is in the display
            if (
                (top >= display_top &&
                top <= display_bottom) ||
                (bottom >= display_top &&
                bottom <= display_bottom) ||
                (top <= display_top &&
                bottom >= display_bottom))
            {
                //we get the display then tell it to composite correctly
                DisplayArea display = cur.GetDisplayArea();
                Rectangle ClipRegion =
                    new Rectangle(
                    0,                                  //start on the left always
                    Math.Max(display_top - top, 0),     //crop non-visible y
                    cur.Width,                          //get the whole width
                    Math.Min(cur.Height - Math.Max(display_top - top, 0), //the height minus the 
                    //top clip
                        Math.Min(display_bottom - top,                    //or the bottom to the top
                                 display_bottom - display_top))           //or the screen at most
                    );
                Point offset = new Point(x,             //offset from top if
                    //needed
                                         Math.Max(top - display_top, 0));

#if DEBUG
                Console.WriteLine("DisplayDocument: \n\tCALCULATED DIMENSEINS AND OFFSET Type:{0}  [X:{1} Y:{2} W:{3} H:{4}] at (X:{5}, Y:{6})",
                                cur.GetType(), ClipRegion.X, ClipRegion.Y,
                                                ClipRegion.Width, ClipRegion.Height,
                                                offset.X, offset.Y);
#endif
                display.Composite(my_graphics.Graphics, ClipRegion, offset);
                //keep a copy because we are actually drawing it
                my_current_displayed.Add(new DisplayElement(cur,
                    new Rectangle(
                       ClipRegion.X + offset.X,
                       ClipRegion.Y + offset.Y,
                       ClipRegion.Width,
                       ClipRegion.Height)));

                int cursor = y;
                //recurse through children
                foreach (Module child in cur.Children)
                {
                    cursor += child.TopSpace;
                    RecursiveDraw(
                        child,
                        cursor + cur.TopBuffer,
                        x + cur.LeftBuffer);
                    cursor += child.Height + child.BottomSpace;
                }
            }



            return bottom;
        }

        #endregion

        #region Setup a document

        /// <summary>
        /// Get a current copy of the XMLDocument that this Control is rendering.  This is really intended
        /// to be used for save events and the like.
        /// </summary>
        /// <returns></returns>
        public XmlDocument GetDocument()
        {
            return Document;
        }

        /// <summary>
        /// Initialize this Renderer with a XmlDocument.
        /// </summary>
        /// <param name="doc">Document to display</param>
        public void SetDocument(XmlDocument doc)
        {
            Changes = new List<ChangeAtom>();
            my_document = doc;

            //don't leave a box there, it won't be valid any longer

            my_modules = new List<Module>();
            for (int i = 0; i < doc.ChildNodes.Count; ++i)
            {
                if ((doc.ChildNodes[i] as XmlElement) != null)
                {
                    Module kid = my_config.GetInstance(
                        doc.ChildNodes[i].Name,
                        doc.ChildNodes[i],
                        null,
                        this);
                    my_modules.Add(kid);
                    kid.Setup(doc.ChildNodes[i]);
                }
            }
            CalculateSpace();
            Redraw();
        }
        #endregion
    }

}
